// driver.cpp
// Driver
// Protea Application Driver / Loads application shared library and prepares for execution
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// Please send correspondence to:
//
// theproteaproject@gmail.com
//

/////
//
// Driver program
//
/////

#include <xercesc/util/TransService.hpp>
#include <xercesc/parsers/XercesDOMParser.hpp>
#include <xercesc/sax/ErrorHandler.hpp>
#include <xercesc/sax/SAXParseException.hpp>
#include <xercesc/dom/DOM.hpp>

#include "../defines.h"
#include "../entry.h"
#include "../AppHandler/AppInterface.h"
#include "../AppHandler/transreg.h"
#include "../AppHandler/Transactions.h"
#include "../AppHandler/printoutput.h"
#include "../OSDep/dir.h"
#include "../OSDep/shlib.h"
#include "../OSDep/files.h"
#include "../OSDep/utf8.h"
#include "driver.h"

#include "../defns.h"
using namespace ProteaApp1;

char* appname = NULL;

// Fix for broken xerces in debug mode
#undef new

class DOMTreeErrorReporter : public XERCES_CPP_NAMESPACE::ErrorHandler
{
public:
    // -----------------------------------------------------------------------
    //  Constructors and Destructor
    // -----------------------------------------------------------------------
    DOMTreeErrorReporter() :
       fSawErrors(false)
    {
    }

    ~DOMTreeErrorReporter()
    {
    }


    // -----------------------------------------------------------------------
    //  Implementation of the error handler interface
    // -----------------------------------------------------------------------
	void warning(const XERCES_CPP_NAMESPACE::SAXParseException& toCatch) { ; };
	void error(const XERCES_CPP_NAMESPACE::SAXParseException& toCatch) {
		fSawErrors = true;
		cerr << "Error at file \"" << UniTranslator(toCatch.getSystemId()).UTF8()
					<< "\", line " << toCatch.getLineNumber()
					<< ", column " << toCatch.getColumnNumber()
			<< "\n   Message: " << UniTranslator(toCatch.getMessage()).UTF8() << endl;
	};

	void fatalError(const XERCES_CPP_NAMESPACE::SAXParseException& toCatch) {
		fSawErrors = true;
		cerr << "Fatal Error at file \"" << UniTranslator(toCatch.getSystemId()).UTF8()
					<< "\", line " << toCatch.getLineNumber()
					<< ", column " << toCatch.getColumnNumber()
			<< "\n   Message: " << UniTranslator(toCatch.getMessage()).UTF8() << endl;
	};

	void resetErrors() {
		fSawErrors = false;
	};

    // -----------------------------------------------------------------------
    //  Getter methods
    // -----------------------------------------------------------------------
	bool getSawErrors() const { return fSawErrors; };

    // -----------------------------------------------------------------------
    //  Private data members
    //
    //  fSawErrors
    //      This is set if we get any errors, and is queryable via a getter
    //      method. Its used by the main code to suppress output if there are
    //      errors.
    // -----------------------------------------------------------------------
    bool    fSawErrors;
};




void XCDriver::StartMessage()
{
	// Send message to substation ... gcs, denoting readiness
	parent->porttype = pt_disp;

	// Grab system hostname
	char* hostname = new char[MAX_PATH];
	XCGetHostName( hostname, MAX_PATH );

	// Create overall message
	ToolsTrace( TRC_APPINTERFACE, TLV_HIGH, "Create message" );
	XCToolsMessage hello( this );
	hello.CreateMsg( DISP_MESSAGE_SIZE + STARTUP_MESSAGE_SIZE + strlen( hostname ) + strlen(process) + 1);
	hello.SetHeader( msg_dispatch, src_app, dest_gcs, false, true );

	// Formulate dispatch message
	ToolsTrace( TRC_APPINTERFACE, TLV_HIGH, "Move data into message" );
	disp_message* disp_info = (disp_message*)hello.GetDataPtr();
	disp_info->opcode = disp_hello;

	// Formulate startup message
	startup_message* startup = (startup_message*)disp_info->data;
	startup->appnum = myappnum;
	startup->pid = XCGetProcessIdentifier();
	startup->filelen = strlen( process );
	sprintf( startup->data, "%s%s", process, hostname );
	delete [] hostname;

	// Send the result message
	ToolsTrace( TRC_APPINTERFACE, TLV_HIGH, "Send message" );
	SendMessage( msg_src, parent, &hello );
};




//
// Verifies that an application is available to take requests
//
bool XCDriver::CheckLockFile() {
	char lockfilename[ 2048 ];
	sprintf( lockfilename, "%sapp%d.lock", ToolsDir, myappnum );

    XCFileTransport lockfile( NULL );
	try {
		lockfile.Open( lockfilename, read_only, open_existing );
	} catch( XCToolsError& err ) {
		err.Clear();
		return true;
	};
	lockfile.Close();
	return false;
};


//////////////////////////////////////////////////////////////////////////////
// Transaction Case Statement
//
XCTransactionInfo* XCDriver::OnTransaction( XCTransactionInfo* transaction ) {
	INFUNC( XCDriver::OnTransaction, this );

	XCTransactionInfo* reply = NULL;

	// If the application is locked, prevent all access to the application
	if (!CheckLockFile()) {
		RAISETOOLSERROR( ERR_APPLICATION_LOCKED );
	};

	// See if the transaction is registered
	XCTransRegTransaction* trans = regsvr.FindTransaction( transaction->GetTransNumber() );
	if (trans) {
		// Increase the transaction call count
		trans->calls++;

		// Execute the actual transaction
		ToolsTrace( TRC_APPINTERFACE, TLV_LOW, "Execute transaction: %d  ID=%d", transaction->GetTransNumber(), transaction->GetTransID() );
		reply = trans->transaction_code( transaction );
	} else {
		RAISETOOLSERROR( ERR_BAD_TXN );
	};

	OUTFUNCRET( reply );
};
//////////////////////////////////////////////////////////////////////////////


void XCDriver::OnDroppedConnection( XCMsgPortPtr tempport ) {
	// We only have a connection to our parent, so abort
	CancelMessageLoop();
};




void XCDriver::RunApplication( long port ) {
	INFUNC( XCDriver::RunApplication, this );

	init_message init;

	// Signal start of the application
	StartApp( (PORT_HANDLE)port, dest_app, init );
	appname = strdup( init.startup_parms.appl_name );
/*
	printf( "##################################\n" );
	printf( "##################################\n" );
	printf( "  Application: %d, %s\n", init.startup_parms.appl_nbr, appname );
	printf( "##################################\n" );
	printf( "##################################\n" );
*/

	// Initialize the application APPDIC files
	Initialize( appname, init.startup_parms.appl_nbr );

/*
	XCAppOptions opts;
	GetOptions( opts );
	opts.debug_type_filter |= TRC_FUNCTIONS;
	for (int i=0; i<10; i++)
		opts.debug_level_cutoff[i] = 255;
	SetOptions( opts );
*/

	// Change our working directory
	sprintf( TransDir, "%s%s", appname, OSFILEDELIM );
	if (!XCFileExists( TransDir ))
		RAISETOOLSERROR_1PARAM( ERR_NO_APP_DIR, appname );

	// Change our default directory
	XCChDir( TransDir );

	DO_TRAFFIC_SECURITY_CHECK;

	// Initialize printed reports
	XCPrintOutput::Initialize();

	// Load in all the shared libraries
	if (!LoadSharedCode( appname ))
		RAISETOOLSERROR_1PARAM( ERR_NO_APP_DIR, appname );

	// Dump hash table statistics
//	regsvr.OutputHashTable();

	// Send a successful start message
	StartMessage();

	// Send Status Message to GCS
	SendStatus( src_app, parent );

	//
	// Wait for and process messages
	//
//	cout << "Waiting for messages loop..." << endl;
	MessageLoop();

	XCPrintOutput::Uninitialize();


	cout << "Program ending" << endl;

	OUTFUNC();
};



bool XCDriver::TryLibrary( const char* library ) {
	XCSharedLibrary* dynamic;
	bool result = true;

//	cout << "Attempt to load: " << library << endl;
	try {
		dynamic = new XCSharedLibrary;
		dynamic->Open( library, true );

		// Find registration function
		if (!regsvr.RegisterLibrary( library, dynamic )) {
			dynamic->Close();
			result = false;
		};
	} catch( XCToolsError& err ) {
		cout << "Unable to load library " << library << endl;
		cout << "\t" << err.GetDescription() << endl;
		err.Clear();
		delete dynamic;
		result = false;
	};

	return result;
};



bool XCDriver::LoadSharedCode( char* program ) {
	INFUNC( XCDriver::LoadSharedCode, this );

	char path[MAX_PATH];
	char filepath[MAX_PATH];

	bool result = false;
	typedef void (*reg_func)( XCTransRegServer* );

	const char* INIT_FILE = "./INIT";

	if (XCFileExists( INIT_FILE )) {
		XERCES_CPP_NAMESPACE::DOMDocument* doc;
		XERCES_CPP_NAMESPACE::DOMNode* node;

		XERCES_CPP_NAMESPACE::XercesDOMParser *parser = new XERCES_CPP_NAMESPACE::XercesDOMParser;

		parser->setIncludeIgnorableWhitespace( false );
		parser->parse( INIT_FILE );

		DOMTreeErrorReporter *errReporter = new DOMTreeErrorReporter();
		parser->setErrorHandler( errReporter );

		doc = parser->getDocument();
		node = doc->getDocumentElement();
		while (node) {
			if (strcmp( UniTranslator(node->getNodeName()), "Files" )==0) {
				XERCES_CPP_NAMESPACE::DOMNode* files = node->getFirstChild();
				while (files) {
					UniTranslator fname( files->getNodeName() );
					if (strcmp( fname, "File" )==0) {
						XERCES_CPP_NAMESPACE::DOMNode* myfile = files->getFirstChild();

						UniTranslator fvalue( myfile->getNodeValue());
						sprintf( filepath, "./%s", fvalue.UTF8() );
						result |= TryLibrary( filepath );
					};

					files = files->getNextSibling();
				};
			};

			node = node->getNextSibling();
		};
	} else {
		XCDirectorySearch dp;

		// Search the application directory for .trans files
		dp.Search( ".", path );
		do {
			if (strstr(path, ".trans")) {
				sprintf( filepath, "./%s", path );
				// Open transaction library
				result |= TryLibrary( filepath );
			}
		} while ( dp.NextFile( path ) );
		dp.Close();
	};

	OUTFUNCRET( result );
};


int main( int argc, char** argv ) {

	Protea::ShowIntro( "driver" );

	if (argc > 1 && !strcmp(argv[1],"-L"))
	{
		Protea::ShowLicense();
		return 0;
	};

	if (argc != 2) {
		cout << "Program may only be executed through a dispatcher!" << endl;
		cout << "Expect 2 parms, got " << argc << endl;
		return 1;
	};

	// Initialize Protea
	Protea::Initialize( true, true );

#ifdef _DEBUG
/*
	// Get current flag
	int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );

	// Turn on leak-checking output bit
	tmpFlag |= _CRTDBG_LEAK_CHECK_DF;

	// Turn something else on
	tmpFlag |= _CRTDBG_ALLOC_MEM_DF;

	// Turn SLOW CHECKING ON???
//	tmpFlag |= _CRTDBG_CHECK_ALWAYS_DF;

	// Set flag to the new value
	_CrtSetDbgFlag( tmpFlag );
*/
#endif

	// Try to read from the pipe!
	{
	XCDriver App;
	try {
		INFUNC( main, NULL )
		App.RunApplication( atoi( argv[1] ) );
		OUTFUNC()
	} catch( XCToolsError& e ) {
		XCOutputCallStack( &App, e.GetErrorNumber(), e.GetDescription() );
	} catch( ... ) {
		XCOutputCallStack( &App, -1, NULL );
	};
	};

	if (appname)
		free( appname );

	Protea::Uninitialize();
	printf( "Driver exited normally\n" );
	return 0;
};
